package docker

import (
	"context"
	"math/rand"
	"strconv"
	"sync"
	"time"

	"chainmaker.org/chainmaker/smarteditor/logger"
	"chainmaker.org/chainmaker/smarteditor/util"
)

const containerCount = 1000000

var log = logger.Get()

//DockerResourceManager manage docker resource
type ContainerManager struct {
	ctx context.Context

	dockerContainerPool sync.Map //map[string]string <uid,container_name>
	userContainerIndex  sync.Map //map[string]int <uid,next_index>

	removeFailedContainer chan string
}

//NewContainerManager new a ContainerManager instance
func NewContainerManager(ctx context.Context) *ContainerManager {
	return &ContainerManager{
		ctx:                   ctx,
		removeFailedContainer: make(chan string, containerCount),
	}
}

//AddRemoveFailedContainer add remove failed docker container
func (rm *ContainerManager) AddRemoveFailedContainer(containerName string) {
	log.Debugf("container manager add container to remove chan")
	rm.removeFailedContainer <- containerName
}

//AddRemoveFailedContainer add remove failed docker container
func (rm *ContainerManager) RemoveUserContainer(uid string) {
	log.Debugf("container manager remove user container. uid:%s", uid)
	containerName, ok := rm.dockerContainerPool.Load(uid)
	if !ok {
		log.Warnf("container manager remove user container, container name not found. uid:%s", uid)
	} else {
		rm.RemoveContainerFromPool(uid)
		rm.removeFailedContainer <- containerName.(string)
	}
}

//AddRemoveFailedContainer add container to container pool
func (rm *ContainerManager) AddContainerToPool(uid, containerName string) {
	log.Debugf("container manager add container to pool. uid:%s, container name:%s", uid, containerName)
	rm.dockerContainerPool.Store(uid, containerName)
}

//RemoveContainerFromPool remove a container from container pool
func (rm *ContainerManager) RemoveContainerFromPool(uid string) {
	log.Debugf("container manager remove container from pool. uid:%s", uid)
	rm.dockerContainerPool.Delete(uid)
}

//GetCacheContainerName get cache container from pool
func (rm *ContainerManager) GetCacheContainerName(uid string) string {
	log.Debugf("container manager get container from pool. uid:%s", uid)
	containerName, ok := rm.dockerContainerPool.Load(uid)
	if !ok {
		return ""
	}
	return containerName.(string)
}

//NextContainerName get next container name for user
func (rm *ContainerManager) NextContainerName(uid string) string {
	log.Debugf("container manager next container name. uid:%s", uid)
	nextIndex, ok := rm.userContainerIndex.Load(uid)
	if !ok {
		nextIndex = 0
	}
	rm.userContainerIndex.Store(uid, nextIndex.(int)+1)
	return "v230" + "_" + uid + "_" + strconv.Itoa(nextIndex.(int))
}

func (rm *ContainerManager) RemoveUnReleaseContainerLoop() {
	for true {
		select {
		case <-rm.ctx.Done():
			log.Debugf("")
		case containerName := <-rm.removeFailedContainer:
			err := util.RemoveDockerContainer(containerName)
			if err != nil {
				log.Debugf("container manager remove container failed. container id :%s, err:%v", containerName, err)
				go func(containerId string) {
					i := (rand.Int() % 60) + 1
					time.Sleep(time.Second * time.Duration(i))
					rm.AddRemoveFailedContainer(containerName)
				}(containerName)
			}
			log.Debugf("container manager remove container success. container id :%s", containerName)
		}
	}
}
